﻿namespace Snipps.Infrastructure
{
    using System;
    using System.Collections;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;

    using FluentValidation;
    using FluentValidation.Internal;
    using FluentValidation.Results;

    public class ValidatableViewModelBase : BindableBase, INotifyDataErrorInfo
    {
        public const string ValidationSummaryPropertyName = "ValidationSummary";

        private readonly List<string> _allErrors;

        private readonly ConcurrentDictionary<string, List<string>> _errors = new ConcurrentDictionary<string, List<string>>();

        private string _validationSummary;

        public ValidatableViewModelBase()
        {
            _allErrors = new List<string>();
        }

        public IValidator Validator { get; set; }

        public bool IsValid
        {
            get
            {
                return !HasErrors;
            }
        }

        public string ValidationSummary
        {
            get
            {
                return _validationSummary;
            }

            private set
            {
                SetProperty(ref _validationSummary, value, ValidationSummaryPropertyName);
            }
        }

        public bool HasErrors
        {
            get
            {
                return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0);
            }
        }

        public IEnumerable GetErrors(string propertyName)
        {
            List<string> errorsForName = null;
            if (propertyName != ValidationSummaryPropertyName)
            {
                if (!string.IsNullOrEmpty(propertyName))
                {
                    _errors.TryGetValue(propertyName, out errorsForName);
                }
            }
            else
            {
                errorsForName = _allErrors;
            }

            return errorsForName;
        }

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public void OnErrorsChanged(string propertyName)
        {
            UpdateValidationSummary();

            var handler = ErrorsChanged;
            if (handler != null)
            {
                handler(this, new DataErrorsChangedEventArgs(propertyName));
                if (propertyName != ValidationSummaryPropertyName)
                {
                    handler(this, new DataErrorsChangedEventArgs(ValidationSummaryPropertyName));
                }
            }
        }

        private void UpdateValidationSummary()
        {
            ValidationSummary = string.Empty;
            _allErrors.Clear();
            var props = _errors.Keys;
            foreach (var prop in props)
            {
                List<string> errorsForProp;
                _errors.TryGetValue(prop, out errorsForProp);
                if (errorsForProp != null)
                {
                    foreach (var error in errorsForProp)
                    {
                        _allErrors.Add(error);
                        ValidationSummary += error + Environment.NewLine;
                    }
                }
            }

            ValidationSummary = ValidationSummary.TrimEnd(Environment.NewLine.ToCharArray());
        }

        public void RemoveErrors(params string[] propertyNames)
        {
            foreach (var propertyName in propertyNames)
            {
                if (_errors.ContainsKey(propertyName))
                {
                    List<string> outLi;
                    _errors.TryRemove(propertyName, out outLi);
                    OnErrorsChanged(propertyName);
                }
            }
        }

        public void RemoveAllErrors()
        {
            _errors.Clear();
        }

        private ValidationResult Validate(IValidator validator, object instance, params string[] properties)
        {
            if (properties.Length > 0)
            {
                var validationContext = new ValidationContext(
                    instance, new PropertyChain(), new MemberNameValidatorSelector(properties));
                return validator.Validate(validationContext);
            }

            return validator.Validate(instance);
        }

        public void Validate(IValidator validator, params string[] propertyNames)
        {
            if (validator == null)
            {
                return;
            }

            var result = Validate(validator, this, propertyNames);
            if (result != null && result.Errors.Any())
            {
                var errors =
                    (from error in result.Errors
                     group error by error.PropertyName
                     into g select new { PropertyName = g.Key, Errors = g.ToList() }).ToList();

                RemoveErrors(errors.Select(x => x.PropertyName).ToArray());

                foreach (var error in errors)
                {
                    var messages = error.Errors.Select(r => r.ErrorMessage).ToList();

                    _errors.TryAdd(error.PropertyName, messages);
                    OnErrorsChanged(error.PropertyName);
                }
            }
            else
            {
                if (!propertyNames.Any())
                {
                    RemoveAllErrors();
                }
                else
                {
                    RemoveErrors(propertyNames);
                }
            }
        }

        public void Validate(string propertyName)
        {
            Validate(Validator, new[] { propertyName });
        }

        protected override void RaisePropertyChanged(string propertyName)
        {
            base.RaisePropertyChanged(propertyName);
            if (propertyName != ValidationSummaryPropertyName)
            {
                RemoveErrors(ValidationSummaryPropertyName);
                Validate(propertyName);
            }
        }
    }
}